/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.util.review.io;

import java.io.File;
import java.io.OutputStream;
import java.net.URI;
import java.util.Calendar;
import java.util.List;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

import com.pearreview.util.review.*;

public class XMLReviewWriter implements ReviewWriter {

	private static final long serialVersionUID = 2941133746162497785L;
	
	public final static String NAMESPACE = 
			"http://www.informatik.uni-stuttgart.de/iste/se";
	private String prefix = "resi";
	
	private javax.xml.datatype.DatatypeFactory factory;
	
	public XMLReviewWriter() throws DatatypeConfigurationException {
		super();
		factory = javax.xml.datatype.DatatypeFactory.newInstance();
	}
	
	public XMLReviewWriter(String prefix) throws 
			DatatypeConfigurationException {
		this();
		setPrefix(prefix);
	}
	
	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}
	
	@Override
	public void write(Review review, File file) throws NullPointerException, 
			TransformerException, ParserConfigurationException {
		write(review, new StreamResult(file));
	}
	
	public void write(Review review, OutputStream outpuStream) 
			throws NullPointerException, TransformerException, 
			ParserConfigurationException {
		write(review, new StreamResult(outpuStream));
	}
	
	public void write(Review review, Result result) throws NullPointerException, 
			TransformerException, ParserConfigurationException {
		Transformer tr = TransformerFactory.newInstance().newTransformer();
		tr.transform(new DOMSource(create(review)), result);
	}
	
	protected Node create(Review review)
			throws ParserConfigurationException, NullPointerException {
		DocumentBuilder docBuilder = 
				DocumentBuilderFactory.newInstance().newDocumentBuilder();
			
		//create document instance
		Document doc = docBuilder.newDocument();
		
		/* review */
		doc.appendChild(createReview(review, doc));
		
		return doc;
	}
	
	protected Element createReview(Review review, Document doc) {
		Element reviewRoot = doc.createElementNS(NAMESPACE, "review");
		reviewRoot.setPrefix(prefix);
		
		/* review name */
		if(review.getName() != null) {
			Element name = doc.createElementNS(NAMESPACE, "name");
			name.setPrefix(prefix);
			Node value = doc.createCDATASection(review.getName());
			name.appendChild(value);
			reviewRoot.appendChild(name);
		}
		
		/* review description */
		if(review.getDescription() != null) {
			Element description = doc.createElementNS(NAMESPACE, "description");
			description.setPrefix(prefix);
			Node value = doc.createCDATASection(review.getDescription());
			description.appendChild(value);
			reviewRoot.appendChild(description);
		}
		
		/* review product */
		if(review.getProduct() != null) {
			Element product = createProduct(review.getProduct(), doc);
			reviewRoot.appendChild(product);
		}
		
		/* review attendees */
		if(review.getAttendees() != null) {
			for (Attendee currentAttendee: review.getAttendees()) {
				if(currentAttendee == null)
					continue;
				Element attendee = createAttendee(currentAttendee, doc);
				reviewRoot.appendChild(attendee);
			}
		}
		
		/* review aspects */
		if(review.getAspects() != null) {
			for (Aspect currentAspect: review.getAspects()) {
				if(currentAspect == null)
					continue;
				Element aspect = createAspect(currentAspect, doc);
				reviewRoot.appendChild(aspect);
			}
		}
		
		/* review severities */
		if(review.getSeverities() != null) {
			Element severities = createSeverities(review.getSeverities(), doc);
			reviewRoot.appendChild(severities);
		}
		
		/* review meeting */
		if(review.getMeetings() != null) {
			for (Meeting currentMeeting: review.getMeetings()) {
				if(currentMeeting == null)
					continue;
				Element meeting = createMeeting(currentMeeting, doc);
				reviewRoot.appendChild(meeting);
			}
		}
		
		/* review impression */
		if(review.getImpression() != null) {
			Element impression = doc.createElementNS(NAMESPACE, "impression");
			impression.setPrefix(prefix);
			Node value = doc.createCDATASection(review.getImpression());
			impression.appendChild(value);
			reviewRoot.appendChild(impression);
		}
		
		/* review recommendation */
		if(review.getRecommendation() != null) {
			Element recommendation = 
					doc.createElementNS(NAMESPACE, "recommendation");
			recommendation.setPrefix(prefix);
			Node value = doc.createCDATASection(review.getRecommendation());
			recommendation.appendChild(value);
			reviewRoot.appendChild(recommendation);
		}
		
		/* review comments */
		if(review.getComments() != null) {
			Element comments = doc.createElementNS(NAMESPACE, "comments");
			comments.setPrefix(prefix);
			Node value = doc.createCDATASection(review.getComments());
			comments.appendChild(value);
			reviewRoot.appendChild(comments);
		}
		
		return reviewRoot;
	}
	
	protected Element createProduct(Product product, Document doc) {
		Element productRoot = doc.createElementNS(NAMESPACE, "product");
		productRoot.setPrefix(prefix);
		
		/* product name */
		if(product.getName() != null) {
			Element name = doc.createElementNS(NAMESPACE, "name");
			name.setPrefix(prefix);
			Node value = doc.createCDATASection(product.getName());
			name.appendChild(value);
			productRoot.appendChild(name);
		}
		
		/* product reference */
		if(product.getReference() != null) {
			for (String currentReference: product.getReference()) {
				if(currentReference == null)
					continue;
				Element reference = doc.createElementNS(NAMESPACE, "reference");
				reference.setPrefix(prefix);
				Node value = doc.createCDATASection(currentReference);
				reference.appendChild(value);
				productRoot.appendChild(reference);
			}
		}
		
		/* product version */
		if(product.getVersion() != null) {
			Element version = doc.createElementNS(NAMESPACE, "version");
			version.setPrefix(prefix);
			Node value = doc.createCDATASection(product.getVersion());
			version.appendChild(value);
			productRoot.appendChild(version);
		}
		
		return productRoot;
	}
	
	protected Element createAttendee(Attendee attendee, Document doc) {
		Element attendeeRoot = doc.createElementNS(NAMESPACE, "attendee");
		attendeeRoot.setPrefix(prefix);
		
		/* attendee id */
		if(attendee.getId() != null) {
			Element id = doc.createElementNS(NAMESPACE, "id");
			id.setPrefix(prefix);
			Text value = doc.createTextNode(attendee.getId());
			id.appendChild(value);
			attendeeRoot.appendChild(id);
		}
		
		/* attendee name */
		if(attendee.getName() != null) {
			Element name = doc.createElementNS(NAMESPACE, "name");
			name.setPrefix(prefix);
			Node value = doc.createCDATASection(attendee.getName());
			name.appendChild(value);
			attendeeRoot.appendChild(name);
		}
		
		/* attendee contact */
		if(attendee.getContact() != null) {
			Element contact = doc.createElementNS(NAMESPACE, "contact");
			contact.setPrefix(prefix);
			Node value = doc.createCDATASection(attendee.getContact());
			contact.appendChild(value);
			attendeeRoot.appendChild(contact);
		}
		
		/* attendee role */
		if(attendee.getRole() != null) {
			Element role = doc.createElementNS(NAMESPACE, "role");
			role.setPrefix(prefix);
			Text value = 
					doc.createTextNode(attendee.getRole().name().toLowerCase());
			role.appendChild(value);
			attendeeRoot.appendChild(role);
		}
		
		/* attendee aspects */
		if(attendee.getAspects() != null) {
			Element aspects = doc.createElementNS(NAMESPACE, "aspects");
			aspects.setPrefix(prefix);
			for (Aspect currentAspect: attendee.getAspects()) {
				if(currentAspect == null)
					continue;
				Element aspectId = doc.createElementNS(NAMESPACE, "aspect-id");
				aspectId.setPrefix(prefix);
				Text value = doc.createTextNode(currentAspect.getId());
				aspectId.appendChild(value);
				aspects.appendChild(aspectId);
			}
			attendeeRoot.appendChild(aspects);
		}
		
		return attendeeRoot;
	}
	
	protected Element createAspect(Aspect aspect, Document doc) {
		Element aspectRoot = doc.createElementNS(NAMESPACE, "aspect");
		aspectRoot.setPrefix(prefix);
		
		/* aspect id */
		if(aspect.getId() != null) {
			Element id = doc.createElementNS(NAMESPACE, "id");
			id.setPrefix(prefix);
			Text value = doc.createTextNode(aspect.getId());
			id.appendChild(value);
			aspectRoot.appendChild(id);
		}
		
		/* aspect category */
		if(aspect.getCategory() != null) {
			Element category = doc.createElementNS(NAMESPACE, "category");
			category.setPrefix(prefix);
			Node value = doc.createCDATASection(aspect.getCategory());
			category.appendChild(value);
			aspectRoot.appendChild(category);
		}
		
		/* aspect directive */
		if(aspect.getDirective() != null) {
			Element directive = doc.createElementNS(NAMESPACE, "directive");
			directive.setPrefix(prefix);
			Node value = doc.createCDATASection(aspect.getDirective());
			directive.appendChild(value);
			aspectRoot.appendChild(directive);
		}
		
		/* aspect description */
		if(aspect.getDescription() != null) {
			Element description = doc.createElementNS(NAMESPACE, "description");
			description.setPrefix(prefix);
			Node value = doc.createCDATASection(aspect.getDescription());
			description.appendChild(value);
			aspectRoot.appendChild(description);
		}
		
		return aspectRoot;
	}
	
	protected Element createSeverities(List<Severity> sev, Document doc) {
		Element severitiesRoot = doc.createElementNS(NAMESPACE, "severities");
		severitiesRoot.setPrefix(prefix);
		
		for(Severity currentSeverity: sev) {
			if(currentSeverity == null)
				continue;
			Element severity = doc.createElementNS(NAMESPACE, "severity");
			severity.setPrefix(prefix);
			Node value = doc.createCDATASection(currentSeverity.getName());
			severity.appendChild(value);
			severitiesRoot.appendChild(severity);
		}
		
		return severitiesRoot;
	}
	
	protected Element createMeeting(Meeting meeting, Document doc) {
		Element meetingRoot = doc.createElementNS(NAMESPACE, "meeting");
		meetingRoot.setPrefix(prefix);
		
		/* meeting planned-date */
		if(meeting.getPlannedDate() != null) {
			Element plannedDate = doc.createElementNS(NAMESPACE, "planned-date");
			plannedDate.setPrefix(prefix);
			
			Calendar c = meeting.getPlannedDate();
			XMLGregorianCalendar gc = factory.newXMLGregorianCalendarDate(
				 	c.get(Calendar.YEAR),
				 	c.get(Calendar.MONTH) + 1, 
				 	c.get(Calendar.DAY_OF_MONTH), 
				 	c.get(Calendar.ZONE_OFFSET) / 60000);
			
			Text value = doc.createTextNode(gc.toString());
			plannedDate.appendChild(value);
			meetingRoot.appendChild(plannedDate);
		}
		
		/* meeting planned-start */
		if(meeting.getPlannedStart() != null) {
			Element plannedStart = doc.createElementNS(NAMESPACE, "planned-start");
			plannedStart.setPrefix(prefix);
			
			Calendar c = meeting.getPlannedStart();
			XMLGregorianCalendar gc = factory.newXMLGregorianCalendarTime(
					c.get(Calendar.HOUR_OF_DAY), 
					c.get(Calendar.MINUTE), 
					c.get(Calendar.SECOND), 
					c.get(Calendar.MILLISECOND), 
					c.get(Calendar.ZONE_OFFSET) / 60000);
			
			Text value = doc.createTextNode(gc.toString());
			plannedStart.appendChild(value);
			meetingRoot.appendChild(plannedStart);
		}
		
		/* meeting planned-end */
		if(meeting.getPlannedEnd() != null) {
			Element plannedEnd = doc.createElementNS(NAMESPACE, "planned-end");
			plannedEnd.setPrefix(prefix);
			
			Calendar c = meeting.getPlannedEnd();
			XMLGregorianCalendar gc = factory.newXMLGregorianCalendarTime(
					c.get(Calendar.HOUR_OF_DAY), 
					c.get(Calendar.MINUTE), 
					c.get(Calendar.SECOND), 
					c.get(Calendar.MILLISECOND), 
					c.get(Calendar.ZONE_OFFSET) / 60000);
			
			Text value = doc.createTextNode(gc.toString());
			plannedEnd.appendChild(value);
			meetingRoot.appendChild(plannedEnd);
		}
		
		/* meeting planned-location */
		if(meeting.getPlannedLocation() != null) {
			Element plannedLocation = 
					doc.createElementNS(NAMESPACE, "planned-location");
			plannedLocation.setPrefix(prefix);
			Node value = doc.createCDATASection(meeting.getPlannedLocation());
			plannedLocation.appendChild(value);
			meetingRoot.appendChild(plannedLocation);
		}
		
		/* meeting protocol */
		if(meeting.getProtocol() != null) {
			Element protocol = createProtocol(meeting.getProtocol(), doc);
			meetingRoot.appendChild(protocol);
		}
		
		/* meeting canceled */
		if(meeting.getCanceled() != null) {
			Element canceled = doc.createElementNS(NAMESPACE, "canceled");
			canceled.setPrefix(prefix);
			Node value = doc.createCDATASection(meeting.getCanceled());
			canceled.appendChild(value);
			meetingRoot.appendChild(canceled);
		}
		
		/* meeting comments */
		if(meeting.getComments() != null) {
			Element comments = doc.createElementNS(NAMESPACE, "comments");
			comments.setPrefix(prefix);
			Node value = doc.createCDATASection(meeting.getComments());
			comments.appendChild(value);
			meetingRoot.appendChild(comments);
		}
		
		return meetingRoot;
	}
	
	protected Element createProtocol(Protocol protocol, Document doc) {
		Element protocolRoot = doc.createElementNS(NAMESPACE, "protocol");
		protocolRoot.setPrefix(prefix);
		
		/* protocol date */
		if(protocol.getDate() != null) {
			Element date = doc.createElementNS(NAMESPACE, "date");
			date.setPrefix(prefix);
			
			Calendar c = protocol.getDate();
			XMLGregorianCalendar gc = factory.newXMLGregorianCalendarDate(
					c.get(Calendar.YEAR),
				 	c.get(Calendar.MONTH) + 1, 
				 	c.get(Calendar.DAY_OF_MONTH), 
				 	c.get(Calendar.ZONE_OFFSET) / 60000);
			
			Text value = doc.createTextNode(gc.toString());
			date.appendChild(value);
			protocolRoot.appendChild(date);
		}
		
		/* protocol start */
		if(protocol.getStart() != null) {
			Element start = doc.createElementNS(NAMESPACE, "start");
			start.setPrefix(prefix);
			
			Calendar c = protocol.getStart();
			XMLGregorianCalendar gc = factory.newXMLGregorianCalendarTime(
					c.get(Calendar.HOUR), 
					c.get(Calendar.MINUTE), 
					c.get(Calendar.SECOND), 
					c.get(Calendar.MILLISECOND), 
					c.get(Calendar.ZONE_OFFSET) / 60000);
			
			Text value = doc.createTextNode(gc.toString());
			start.appendChild(value);
			protocolRoot.appendChild(start);
		}
		
		/* protocol end */
		if(protocol.getEnd() != null) {
			Element end = doc.createElementNS(NAMESPACE, "end");
			end.setPrefix(prefix);
			
			Calendar c = protocol.getEnd();
			XMLGregorianCalendar gc = factory.newXMLGregorianCalendarTime(
					c.get(Calendar.HOUR), 
					c.get(Calendar.MINUTE), 
					c.get(Calendar.SECOND), 
					c.get(Calendar.MILLISECOND), 
					c.get(Calendar.ZONE_OFFSET) / 60000);
			
			Text value = doc.createTextNode(gc.toString());
			end.appendChild(value);
			protocolRoot.appendChild(end);
		}
		
		/* protocol location */
		if(protocol.getLocation() != null) {
			Element location = doc.createElementNS(NAMESPACE, "location");
			location.setPrefix(prefix);
			Node value = doc.createCDATASection(protocol.getLocation());
			location.appendChild(value);
			protocolRoot.appendChild(location);
		}
		
		/* protocol (available) attendees */
		if(protocol.getAvailableAttendees() != null) {
			for(AvailableAttendee cAtt: protocol.getAvailableAttendees()) {
				if(cAtt == null)
					continue;
				Element attendee = createAvailableAttendee(cAtt, doc);
				protocolRoot.appendChild(attendee);
			}
		}
		
		/* protocol findings */
		if(protocol.getFindings() != null) {
			for(Finding currentFinding: protocol.getFindings()) {
				if(currentFinding == null)
					continue;
				Element finding = createFinding(currentFinding, doc);
				protocolRoot.appendChild(finding);
			}
		}
		
		/* protocol comments */
		if(protocol.getComments() != null) {
			Element comments = doc.createElementNS(NAMESPACE, "comments");
			comments.setPrefix(prefix);
			Node value = doc.createCDATASection(protocol.getComments());
			comments.appendChild(value);
			protocolRoot.appendChild(comments);
		}
		
		return protocolRoot;
	}
	
	protected Element createAvailableAttendee(AvailableAttendee attendee, 
			Document doc) {
		Element attendeeRoot = doc.createElementNS(NAMESPACE, "attendee");
		attendeeRoot.setPrefix(prefix);
		
		/* attendee id */
		if(attendee.getId() != null) {
			Element attendeeId = doc.createElementNS(NAMESPACE, "attendee");
			attendeeId.setPrefix(prefix);
			Text value = doc.createTextNode(attendee.getId());
			attendeeId.appendChild(value);
			attendeeRoot.appendChild(attendeeId);
		}
		
		/* attendee preparation-time */
		if(attendee.getPreparationTime() != null) {
			Element preparationTime = 
					doc.createElementNS(NAMESPACE, "preparation-time");
			preparationTime.setPrefix(prefix);
			Duration pt = factory.newDuration(attendee.getPreparationTime());
			Text value = doc.createTextNode(pt.toString());
			preparationTime.appendChild(value);
			attendeeRoot.appendChild(preparationTime);
		}
		
		return attendeeRoot;
	}
	
	protected Element createFinding(Finding finding, Document doc) {
		Element findingRoot = doc.createElementNS(NAMESPACE, "finding");
		findingRoot.setPrefix(prefix);
		
		/* finding id */
		if(finding.getId() != null) {
			Element id = doc.createElementNS(NAMESPACE, "id");
			id.setPrefix(prefix);
			Text value = doc.createTextNode(finding.getId());
			id.appendChild(value);
			findingRoot.appendChild(id);
		}
		
		/* finding severity */
		if(finding.getSeverity().getName() != null) {
			Element severity = doc.createElementNS(NAMESPACE, "severity");
			severity.setPrefix(prefix);
			Node value = 
					doc.createCDATASection(finding.getSeverity().getName());
			severity.appendChild(value);
			findingRoot.appendChild(severity);
		}
		
		/* finding description */
		if(finding.getDescription() != null) {
			Element description = doc.createElementNS(NAMESPACE, "description");
			description.setPrefix(prefix);
			Node value = doc.createCDATASection(finding.getDescription());
			description.appendChild(value);
			findingRoot.appendChild(description);
		}
		
		/* finding reference */
		if(finding.getReferences() != null) {
			for(String currentReference: finding.getReferences()) {
				if(currentReference == null)
					continue;
				Element reference = doc.createElementNS(NAMESPACE, "reference");
				reference.setPrefix(prefix);
				Node value = doc.createCDATASection(currentReference);
				reference.appendChild(value);
				findingRoot.appendChild(reference);
			}
		}
		
		/* finding aspect */
		if(finding.getAspects() != null) {
			for(String currentAspect: finding.getAspects()) {
				if(currentAspect == null) 
					continue;
				Element aspect = doc.createElementNS(NAMESPACE, "aspect");
				aspect.setPrefix(prefix);
				Node value = doc.createCDATASection(currentAspect);
				aspect.appendChild(value);
				findingRoot.appendChild(aspect);
			}
		}
		
		/* finding external reference */
		if(finding.getExternalReferences() != null) {
			for(URI currentURI: finding.getExternalReferences()) {
				if(currentURI == null) 
					continue;
				Element uri = 
						doc.createElementNS(NAMESPACE, "external-reference");
				uri.setPrefix(prefix);
				Node value = doc.createCDATASection(currentURI.toString());
				uri.appendChild(value);
				findingRoot.appendChild(uri);
			}
		}
		
		return findingRoot;
	}
}
